﻿namespace FastColoredTextBoxNS
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Text.RegularExpressions;

    /// <summary>
    /// 	Diapason of text chars
    /// </summary>
    public class Range : IEnumerable<Place>
    {
        private Place start;

        private Place end;

        public readonly FastColoredTextBox tb;

        private int preferedPos = -1;

        private int updating;

        private string cachedText;

        private List<Place> cachedCharIndexToPlace;

        private int cachedTextVersion = -1;

        /// <summary>
        /// 	Constructor
        /// </summary>
        public Range(FastColoredTextBox tb)
        {
            this.tb = tb;
        }

        /// <summary>
        /// 	Return true if no selected text
        /// </summary>
        public virtual bool IsEmpty
        {
            get
            {
                if (this.ColumnSelectionMode)
                {
                    return this.Start.iChar == this.End.iChar;
                }
                return this.Start == this.End;
            }
        }

        private bool columnSelectionMode;

        /// <summary>
        /// 	Column selection mode
        /// </summary>
        public bool ColumnSelectionMode
        {
            get
            {
                return this.columnSelectionMode;
            }
            set
            {
                this.columnSelectionMode = value;
            }
        }

        /// <summary>
        /// 	Constructor
        /// </summary>
        public Range(FastColoredTextBox tb, int iStartChar, int iStartLine, int iEndChar, int iEndLine)
            : this(tb)
        {
            this.start = new Place(iStartChar, iStartLine);
            this.end = new Place(iEndChar, iEndLine);
        }

        /// <summary>
        /// 	Constructor
        /// </summary>
        public Range(FastColoredTextBox tb, Place start, Place end)
            : this(tb)
        {
            this.start = start;
            this.end = end;
        }

        public bool Contains(Place place)
        {
            if (place.iLine < Math.Min(this.start.iLine, this.end.iLine))
            {
                return false;
            }
            if (place.iLine > Math.Max(this.start.iLine, this.end.iLine))
            {
                return false;
            }

            Place s = this.start;
            Place e = this.end;
            //normalize start and end
            if (s.iLine > e.iLine || (s.iLine == e.iLine && s.iChar > e.iChar))
            {
                Place temp = s;
                s = e;
                e = temp;
            }

            if (this.columnSelectionMode)
            {
                if (place.iChar < s.iChar || place.iChar > e.iChar)
                {
                    return false;
                }
            }
            else
            {
                if (place.iLine == s.iLine && place.iChar < s.iChar)
                {
                    return false;
                }
                if (place.iLine == e.iLine && place.iChar > e.iChar)
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// 	Returns intersection with other range,
        /// 	empty range returned otherwise
        /// </summary>
        /// <param name="range"> </param>
        /// <returns> </returns>
        public virtual Range GetIntersectionWith(Range range)
        {
            if (this.ColumnSelectionMode)
            {
                return this.GetIntersectionWith_ColumnSelectionMode(range);
            }

            Range r1 = this.Clone();
            Range r2 = range.Clone();
            r1.Normalize();
            r2.Normalize();
            Place newStart = r1.Start > r2.Start ? r1.Start : r2.Start;
            Place newEnd = r1.End < r2.End ? r1.End : r2.End;
            if (newEnd < newStart)
            {
                return new Range(this.tb, this.start, this.start);
            }
            return this.tb.GetRange(newStart, newEnd);
        }

        /// <summary>
        /// 	Returns union with other range.
        /// </summary>
        /// <param name="range"> </param>
        /// <returns> </returns>
        public Range GetUnionWith(Range range)
        {
            Range r1 = this.Clone();
            Range r2 = range.Clone();
            r1.Normalize();
            r2.Normalize();
            Place newStart = r1.Start < r2.Start ? r1.Start : r2.Start;
            Place newEnd = r1.End > r2.End ? r1.End : r2.End;

            return this.tb.GetRange(newStart, newEnd);
        }

        /// <summary>
        /// 	Select all chars of control
        /// </summary>
        public void SelectAll()
        {
            this.ColumnSelectionMode = false;

            this.Start = new Place(0, 0);
            if (this.tb.LinesCount == 0)
            {
                this.Start = new Place(0, 0);
            }
            else
            {
                this.end = new Place(0, 0);
                this.start = new Place(this.tb[this.tb.LinesCount - 1].Count, this.tb.LinesCount - 1);
            }
            if (this == this.tb.Selection)
            {
                this.tb.Invalidate();
            }
        }

        /// <summary>
        /// 	Start line and char position
        /// </summary>
        public Place Start
        {
            get
            {
                return this.start;
            }
            set
            {
                this.end = this.start = value;
                this.preferedPos = -1;
                this.OnSelectionChanged();
            }
        }

        /// <summary>
        /// 	Finish line and char position
        /// </summary>
        public Place End
        {
            get
            {
                return this.end;
            }
            set
            {
                this.end = value;
                this.OnSelectionChanged();
            }
        }

        /// <summary>
        /// 	Text of range
        /// </summary>
        /// <remarks>
        /// 	This property has not 'set' accessor because undo/redo stack works only with 
        /// 	FastColoredTextBox.Selection range. So, if you want to set text, you need to use FastColoredTextBox.Selection
        /// 	and FastColoredTextBox.InsertText() mehtod.
        /// </remarks>
        public virtual string Text
        {
            get
            {
                if (this.ColumnSelectionMode)
                {
                    return this.Text_ColumnSelectionMode;
                }

                int fromLine = Math.Min(this.end.iLine, this.start.iLine);
                int toLine = Math.Max(this.end.iLine, this.start.iLine);
                int fromChar = this.FromX;
                int toChar = this.ToX;
                if (fromLine < 0)
                {
                    return null;
                }
                //
                var sb = new StringBuilder();
                for (int y = fromLine; y <= toLine; y++)
                {
                    int fromX = y == fromLine ? fromChar : 0;
                    int toX = y == toLine ? Math.Min(this.tb[y].Count - 1, toChar - 1) : this.tb[y].Count - 1;
                    for (int x = fromX; x <= toX; x++)
                    {
                        sb.Append(this.tb[y][x].c);
                    }
                    if (y != toLine && fromLine != toLine)
                    {
                        sb.AppendLine();
                    }
                }
                return sb.ToString();
            }
        }

        internal void GetText(out string text, out List<Place> charIndexToPlace)
        {
            //try get cached text
            if (this.tb.TextVersion == this.cachedTextVersion)
            {
                text = this.cachedText;
                charIndexToPlace = this.cachedCharIndexToPlace;
                return;
            }
            //
            int fromLine = Math.Min(this.end.iLine, this.start.iLine);
            int toLine = Math.Max(this.end.iLine, this.start.iLine);
            int fromChar = this.FromX;
            int toChar = this.ToX;

            var sb = new StringBuilder((toLine - fromLine) * 50);
            charIndexToPlace = new List<Place>(sb.Capacity);
            if (fromLine >= 0)
            {
                for (int y = fromLine; y <= toLine; y++)
                {
                    int fromX = y == fromLine ? fromChar : 0;
                    int toX = y == toLine ? Math.Min(toChar - 1, this.tb[y].Count - 1) : this.tb[y].Count - 1;
                    for (int x = fromX; x <= toX; x++)
                    {
                        sb.Append(this.tb[y][x].c);
                        charIndexToPlace.Add(new Place(x, y));
                    }
                    if (y != toLine && fromLine != toLine)
                    {
                        foreach (var c in Environment.NewLine)
                        {
                            sb.Append(c);
                            charIndexToPlace.Add(new Place(this.tb[y].Count /*???*/, y));
                        }
                    }
                }
            }
            text = sb.ToString();
            charIndexToPlace.Add(this.End > this.Start ? this.End : this.Start);
            //caching
            this.cachedText = text;
            this.cachedCharIndexToPlace = charIndexToPlace;
            this.cachedTextVersion = this.tb.TextVersion;
        }

        /// <summary>
        /// 	Returns first char after Start place
        /// </summary>
        public char CharAfterStart
        {
            get
            {
                if (this.Start.iChar >= this.tb[this.Start.iLine].Count)
                {
                    return '\n';
                }
                return this.tb[this.Start.iLine][this.Start.iChar].c;
            }
        }

        /// <summary>
        /// 	Returns first char before Start place
        /// </summary>
        public char CharBeforeStart
        {
            get
            {
                if (this.Start.iChar > this.tb[this.Start.iLine].Count)
                {
                    return '\n';
                }
                if (this.Start.iChar <= 0)
                {
                    return '\n';
                }
                return this.tb[this.Start.iLine][this.Start.iChar - 1].c;
            }
        }

        /// <summary>
        /// 	Clone range
        /// </summary>
        /// <returns> </returns>
        public Range Clone()
        {
            return (Range)this.MemberwiseClone();
        }

        /// <summary>
        /// 	Return minimum of end.X and start.X
        /// </summary>
        internal int FromX
        {
            get
            {
                if (this.end.iLine < this.start.iLine)
                {
                    return this.end.iChar;
                }
                if (this.end.iLine > this.start.iLine)
                {
                    return this.start.iChar;
                }
                return Math.Min(this.end.iChar, this.start.iChar);
            }
        }

        /// <summary>
        /// 	Return maximum of end.X and start.X
        /// </summary>
        internal int ToX
        {
            get
            {
                if (this.end.iLine < this.start.iLine)
                {
                    return this.start.iChar;
                }
                if (this.end.iLine > this.start.iLine)
                {
                    return this.end.iChar;
                }
                return Math.Max(this.end.iChar, this.start.iChar);
            }
        }

        public int FromLine
        {
            get
            {
                return Math.Min(this.Start.iLine, this.End.iLine);
            }
        }

        public int ToLine
        {
            get
            {
                return Math.Max(this.Start.iLine, this.End.iLine);
            }
        }

        /// <summary>
        /// 	Move range right
        /// </summary>
        /// <remarks>
        /// 	This method jump over folded blocks
        /// </remarks>
        public bool GoRight()
        {
            Place prevStart = this.start;
            this.GoRight(false);
            return prevStart != this.start;
        }

        /// <summary>
        /// 	Move range left
        /// </summary>
        /// <remarks>
        /// 	This method can to go inside folded blocks
        /// </remarks>
        public virtual bool GoRightThroughFolded()
        {
            if (this.ColumnSelectionMode)
            {
                return this.GoRightThroughFolded_ColumnSelectionMode();
            }

            if (this.start.iLine >= this.tb.LinesCount - 1 && this.start.iChar >= this.tb[this.tb.LinesCount - 1].Count)
            {
                return false;
            }

            if (this.start.iChar < this.tb[this.start.iLine].Count)
            {
                this.start.Offset(1, 0);
            }
            else
            {
                this.start = new Place(0, this.start.iLine + 1);
            }

            this.preferedPos = -1;
            this.end = this.start;
            this.OnSelectionChanged();
            return true;
        }

        /// <summary>
        /// 	Move range left
        /// </summary>
        /// <remarks>
        /// 	This method jump over folded blocks
        /// </remarks>
        public bool GoLeft()
        {
            this.ColumnSelectionMode = false;

            Place prevStart = this.start;
            this.GoLeft(false);
            return prevStart != this.start;
        }

        /// <summary>
        /// 	Move range left
        /// </summary>
        /// <remarks>
        /// 	This method can to go inside folded blocks
        /// </remarks>
        public bool GoLeftThroughFolded()
        {
            this.ColumnSelectionMode = false;

            if (this.start.iChar == 0 && this.start.iLine == 0)
            {
                return false;
            }

            if (this.start.iChar > 0)
            {
                this.start.Offset(-1, 0);
            }
            else
            {
                this.start = new Place(this.tb[this.start.iLine - 1].Count, this.start.iLine - 1);
            }

            this.preferedPos = -1;
            this.end = this.start;
            this.OnSelectionChanged();
            return true;
        }

        public void GoLeft(bool shift)
        {
            this.ColumnSelectionMode = false;

            if (!shift)
            {
                if (this.start > this.end)
                {
                    this.Start = this.End;
                    return;
                }
            }

            if (this.start.iChar != 0 || this.start.iLine != 0)
            {
                if (this.start.iChar > 0 && this.tb.LineInfos[this.start.iLine].VisibleState == VisibleState.Visible)
                {
                    this.start.Offset(-1, 0);
                }
                else
                {
                    int i = this.tb.FindPrevVisibleLine(this.start.iLine);
                    if (i == this.start.iLine)
                    {
                        return;
                    }
                    this.start = new Place(this.tb[i].Count, i);
                }
            }

            if (!shift)
            {
                this.end = this.start;
            }

            this.OnSelectionChanged();

            this.preferedPos = -1;
        }

        public void GoRight(bool shift)
        {
            this.ColumnSelectionMode = false;

            if (!shift)
            {
                if (this.start < this.end)
                {
                    this.Start = this.End;
                    return;
                }
            }

            if (this.start.iLine < this.tb.LinesCount - 1 || this.start.iChar < this.tb[this.tb.LinesCount - 1].Count)
            {
                if (this.start.iChar < this.tb[this.start.iLine].Count
                    && this.tb.LineInfos[this.start.iLine].VisibleState == VisibleState.Visible)
                {
                    this.start.Offset(1, 0);
                }
                else
                {
                    int i = this.tb.FindNextVisibleLine(this.start.iLine);
                    if (i == this.start.iLine)
                    {
                        return;
                    }
                    this.start = new Place(0, i);
                }
            }

            if (!shift)
            {
                this.end = this.start;
            }

            this.OnSelectionChanged();

            this.preferedPos = -1;
        }

        internal void GoUp(bool shift)
        {
            this.ColumnSelectionMode = false;

            if (!shift)
            {
                if (this.start.iLine > this.end.iLine)
                {
                    this.Start = this.End;
                    return;
                }
            }

            if (this.preferedPos < 0)
            {
                this.preferedPos = this.start.iChar
                                   -
                                   this.tb.LineInfos[this.start.iLine].GetWordWrapStringStartPosition(
                                       this.tb.LineInfos[this.start.iLine].GetWordWrapStringIndex(this.start.iChar));
            }

            int iWW = this.tb.LineInfos[this.start.iLine].GetWordWrapStringIndex(this.start.iChar);
            if (iWW == 0)
            {
                if (this.start.iLine <= 0)
                {
                    return;
                }
                int i = this.tb.FindPrevVisibleLine(this.start.iLine);
                if (i == this.start.iLine)
                {
                    return;
                }
                this.start.iLine = i;
                iWW = this.tb.LineInfos[this.start.iLine].WordWrapStringsCount;
            }

            if (iWW > 0)
            {
                int finish = this.tb.LineInfos[this.start.iLine].GetWordWrapStringFinishPosition(
                    iWW - 1, this.tb[this.start.iLine]);
                this.start.iChar = this.tb.LineInfos[this.start.iLine].GetWordWrapStringStartPosition(iWW - 1)
                                   + this.preferedPos;
                if (this.start.iChar > finish + 1)
                {
                    this.start.iChar = finish + 1;
                }
            }

            if (!shift)
            {
                this.end = this.start;
            }

            this.OnSelectionChanged();
        }

        internal void GoPageUp(bool shift)
        {
            this.ColumnSelectionMode = false;

            if (this.preferedPos < 0)
            {
                this.preferedPos = this.start.iChar
                                   -
                                   this.tb.LineInfos[this.start.iLine].GetWordWrapStringStartPosition(
                                       this.tb.LineInfos[this.start.iLine].GetWordWrapStringIndex(this.start.iChar));
            }

            int pageHeight = this.tb.ClientRectangle.Height / this.tb.CharHeight - 1;

            for (int i = 0; i < pageHeight; i++)
            {
                int iWW = this.tb.LineInfos[this.start.iLine].GetWordWrapStringIndex(this.start.iChar);
                if (iWW == 0)
                {
                    if (this.start.iLine <= 0)
                    {
                        break;
                    }
                    //pass hidden
                    int newLine = this.tb.FindPrevVisibleLine(this.start.iLine);
                    if (newLine == this.start.iLine)
                    {
                        break;
                    }
                    this.start.iLine = newLine;
                    iWW = this.tb.LineInfos[this.start.iLine].WordWrapStringsCount;
                }

                if (iWW > 0)
                {
                    int finish = this.tb.LineInfos[this.start.iLine].GetWordWrapStringFinishPosition(
                        iWW - 1, this.tb[this.start.iLine]);
                    this.start.iChar = this.tb.LineInfos[this.start.iLine].GetWordWrapStringStartPosition(iWW - 1)
                                       + this.preferedPos;
                    if (this.start.iChar > finish + 1)
                    {
                        this.start.iChar = finish + 1;
                    }
                }
            }

            if (!shift)
            {
                this.end = this.start;
            }

            this.OnSelectionChanged();
        }

        internal void GoDown(bool shift)
        {
            this.ColumnSelectionMode = false;

            if (!shift)
            {
                if (this.start.iLine < this.end.iLine)
                {
                    this.Start = this.End;
                    return;
                }
            }

            if (this.preferedPos < 0)
            {
                this.preferedPos = this.start.iChar
                                   -
                                   this.tb.LineInfos[this.start.iLine].GetWordWrapStringStartPosition(
                                       this.tb.LineInfos[this.start.iLine].GetWordWrapStringIndex(this.start.iChar));
            }

            int iWW = this.tb.LineInfos[this.start.iLine].GetWordWrapStringIndex(this.start.iChar);
            if (iWW >= this.tb.LineInfos[this.start.iLine].WordWrapStringsCount - 1)
            {
                if (this.start.iLine >= this.tb.LinesCount - 1)
                {
                    return;
                }
                //pass hidden
                int i = this.tb.FindNextVisibleLine(this.start.iLine);
                if (i == this.start.iLine)
                {
                    return;
                }
                this.start.iLine = i;
                iWW = -1;
            }

            if (iWW < this.tb.LineInfos[this.start.iLine].WordWrapStringsCount - 1)
            {
                int finish = this.tb.LineInfos[this.start.iLine].GetWordWrapStringFinishPosition(
                    iWW + 1, this.tb[this.start.iLine]);
                this.start.iChar = this.tb.LineInfos[this.start.iLine].GetWordWrapStringStartPosition(iWW + 1)
                                   + this.preferedPos;
                if (this.start.iChar > finish + 1)
                {
                    this.start.iChar = finish + 1;
                }
            }

            if (!shift)
            {
                this.end = this.start;
            }

            this.OnSelectionChanged();
        }

        internal void GoPageDown(bool shift)
        {
            this.ColumnSelectionMode = false;

            if (this.preferedPos < 0)
            {
                this.preferedPos = this.start.iChar
                                   -
                                   this.tb.LineInfos[this.start.iLine].GetWordWrapStringStartPosition(
                                       this.tb.LineInfos[this.start.iLine].GetWordWrapStringIndex(this.start.iChar));
            }

            int pageHeight = this.tb.ClientRectangle.Height / this.tb.CharHeight - 1;

            for (int i = 0; i < pageHeight; i++)
            {
                int iWW = this.tb.LineInfos[this.start.iLine].GetWordWrapStringIndex(this.start.iChar);
                if (iWW >= this.tb.LineInfos[this.start.iLine].WordWrapStringsCount - 1)
                {
                    if (this.start.iLine >= this.tb.LinesCount - 1)
                    {
                        break;
                    }
                    //pass hidden
                    int newLine = this.tb.FindNextVisibleLine(this.start.iLine);
                    if (newLine == this.start.iLine)
                    {
                        break;
                    }
                    this.start.iLine = newLine;
                    iWW = -1;
                }

                if (iWW < this.tb.LineInfos[this.start.iLine].WordWrapStringsCount - 1)
                {
                    int finish = this.tb.LineInfos[this.start.iLine].GetWordWrapStringFinishPosition(
                        iWW + 1, this.tb[this.start.iLine]);
                    this.start.iChar = this.tb.LineInfos[this.start.iLine].GetWordWrapStringStartPosition(iWW + 1)
                                       + this.preferedPos;
                    if (this.start.iChar > finish + 1)
                    {
                        this.start.iChar = finish + 1;
                    }
                }
            }

            if (!shift)
            {
                this.end = this.start;
            }

            this.OnSelectionChanged();
        }

        internal void GoHome(bool shift)
        {
            this.ColumnSelectionMode = false;

            if (this.start.iLine < 0)
            {
                return;
            }

            if (this.tb.LineInfos[this.start.iLine].VisibleState != VisibleState.Visible)
            {
                return;
            }

            this.start = new Place(0, this.start.iLine);

            if (!shift)
            {
                this.end = this.start;
            }

            this.OnSelectionChanged();

            this.preferedPos = -1;
        }

        internal void GoEnd(bool shift)
        {
            this.ColumnSelectionMode = false;

            if (this.start.iLine < 0)
            {
                return;
            }
            if (this.tb.LineInfos[this.start.iLine].VisibleState != VisibleState.Visible)
            {
                return;
            }

            this.start = new Place(this.tb[this.start.iLine].Count, this.start.iLine);

            if (!shift)
            {
                this.end = this.start;
            }

            this.OnSelectionChanged();

            this.preferedPos = -1;
        }

        /// <summary>
        /// 	Set style for range
        /// </summary>
        public void SetStyle(Style style)
        {
            //search code for style
            int code = this.tb.GetOrSetStyleLayerIndex(style);
            //set code to chars
            this.SetStyle(ToStyleIndex(code));
            //
            this.tb.Invalidate();
        }

        /// <summary>
        /// 	Set style for given regex pattern
        /// </summary>
        public void SetStyle(Style style, string regexPattern)
        {
            //search code for style
            StyleIndex layer = ToStyleIndex(this.tb.GetOrSetStyleLayerIndex(style));
            SetStyle(layer, regexPattern, RegexOptions.None);
        }

        /// <summary>
        /// 	Set style for given regex
        /// </summary>
        public void SetStyle(Style style, Regex regex)
        {
            //search code for style
            StyleIndex layer = ToStyleIndex(this.tb.GetOrSetStyleLayerIndex(style));
            SetStyle(layer, regex);
        }

        /// <summary>
        /// 	Set style for given regex pattern
        /// </summary>
        public void SetStyle(Style style, string regexPattern, RegexOptions options)
        {
            //search code for style
            StyleIndex layer = ToStyleIndex(this.tb.GetOrSetStyleLayerIndex(style));
            SetStyle(layer, regexPattern, options);
        }

        /// <summary>
        /// 	Set style for given regex pattern
        /// </summary>
        public void SetStyle(StyleIndex styleLayer, string regexPattern, RegexOptions options)
        {
            if (Math.Abs(this.Start.iLine - this.End.iLine) > 1000)
            {
                options |= SyntaxHighlighter.RegexCompiledOption;
            }
            //
            foreach (var range in this.GetRanges(regexPattern, options))
            {
                range.SetStyle(styleLayer);
            }
            //
            this.tb.Invalidate();
        }

        /// <summary>
        /// 	Set style for given regex pattern
        /// </summary>
        public void SetStyle(StyleIndex styleLayer, Regex regex)
        {
            foreach (var range in GetRanges(regex))
            {
                range.SetStyle(styleLayer);
            }
            //
            this.tb.Invalidate();
        }

        /// <summary>
        /// 	Appends style to chars of range
        /// </summary>
        public void SetStyle(StyleIndex styleIndex)
        {
            //set code to chars
            int fromLine = Math.Min(this.End.iLine, this.Start.iLine);
            int toLine = Math.Max(this.End.iLine, this.Start.iLine);
            int fromChar = this.FromX;
            int toChar = this.ToX;
            if (fromLine < 0)
            {
                return;
            }
            //
            for (int y = fromLine; y <= toLine; y++)
            {
                int fromX = y == fromLine ? fromChar : 0;
                int toX = y == toLine ? Math.Min(toChar - 1, this.tb[y].Count - 1) : this.tb[y].Count - 1;
                for (int x = fromX; x <= toX; x++)
                {
                    Char c = this.tb[y][x];
                    c.style |= styleIndex;
                    this.tb[y][x] = c;
                }
            }
        }

        /// <summary>
        /// 	Sets folding markers
        /// </summary>
        /// <param name="startFoldingPattern"> Pattern for start folding line </param>
        /// <param name="finishFoldingPattern"> Pattern for finish folding line </param>
        public void SetFoldingMarkers(string startFoldingPattern, string finishFoldingPattern)
        {
            this.SetFoldingMarkers(startFoldingPattern, finishFoldingPattern, SyntaxHighlighter.RegexCompiledOption);
        }

        /// <summary>
        /// 	Sets folding markers
        /// </summary>
        /// <param name="startFoldingPattern"> Pattern for start folding line </param>
        /// <param name="finishFoldingPattern"> Pattern for finish folding line </param>
        public void SetFoldingMarkers(string startFoldingPattern, string finishFoldingPattern, RegexOptions options)
        {
            if (startFoldingPattern == finishFoldingPattern)
            {
                SetFoldingMarkers(startFoldingPattern, options);
                return;
            }

            foreach (var range in this.GetRanges(startFoldingPattern, options))
            {
                this.tb[range.Start.iLine].FoldingStartMarker = startFoldingPattern;
            }

            foreach (var range in this.GetRanges(finishFoldingPattern, options))
            {
                this.tb[range.Start.iLine].FoldingEndMarker = startFoldingPattern;
            }
            //
            this.tb.Invalidate();
        }

        /// <summary>
        /// 	Sets folding markers
        /// </summary>
        /// <param name="startEndFoldingPattern"> Pattern for start and end folding line </param>
        public void SetFoldingMarkers(string foldingPattern, RegexOptions options)
        {
            foreach (var range in this.GetRanges(foldingPattern, options))
            {
                if (range.Start.iLine > 0)
                {
                    this.tb[range.Start.iLine - 1].FoldingEndMarker = foldingPattern;
                }
                this.tb[range.Start.iLine].FoldingStartMarker = foldingPattern;
            }

            this.tb.Invalidate();
        }

        /// <summary>
        /// 	Finds ranges for given regex pattern
        /// </summary>
        /// <param name="regexPattern"> Regex pattern </param>
        /// <returns> Enumeration of ranges </returns>
        public IEnumerable<Range> GetRanges(string regexPattern)
        {
            return this.GetRanges(regexPattern, RegexOptions.None);
        }

        /// <summary>
        /// 	Finds ranges for given regex pattern
        /// </summary>
        /// <param name="regexPattern"> Regex pattern </param>
        /// <returns> Enumeration of ranges </returns>
        public IEnumerable<Range> GetRanges(string regexPattern, RegexOptions options)
        {
            //get text
            string text;
            List<Place> charIndexToPlace;
            this.GetText(out text, out charIndexToPlace);
            //create regex
            var regex = new Regex(regexPattern, options);
            //
            foreach (Match m in regex.Matches(text))
            {
                var r = new Range(this.tb);
                //try get 'range' group, otherwise use group 0
                Group group = m.Groups["range"];
                if (!group.Success)
                {
                    group = m.Groups[0];
                }
                //
                r.Start = charIndexToPlace[group.Index];
                r.End = charIndexToPlace[group.Index + group.Length];
                yield return r;
            }
        }

        /// <summary>
        /// 	Finds ranges for given regex pattern.
        /// 	Search is separately in each line.
        /// 	This method requires less memory than GetRanges().
        /// </summary>
        /// <param name="regexPattern"> Regex pattern </param>
        /// <returns> Enumeration of ranges </returns>
        public IEnumerable<Range> GetRangesByLines(string regexPattern, RegexOptions options)
        {
            var regex = new Regex(regexPattern, options);
            foreach (var r in this.GetRangesByLines(regex))
            {
                yield return r;
            }
        }

        /// <summary>
        /// 	Finds ranges for given regex.
        /// 	Search is separately in each line.
        /// 	This method requires less memory than GetRanges().
        /// </summary>
        /// <param name="regex"> Regex </param>
        /// <returns> Enumeration of ranges </returns>
        public IEnumerable<Range> GetRangesByLines(Regex regex)
        {
            this.Normalize();

            var fts = this.tb.TextSource as FileTextSource; //<----!!!! ugly

            //enumaerate lines
            for (int iLine = this.Start.iLine; iLine <= this.End.iLine; iLine++)
            {
                //
                bool isLineLoaded = fts != null ? fts.IsLineLoaded(iLine) : true;
                //
                var r = new Range(this.tb, new Place(0, iLine), new Place(this.tb[iLine].Count, iLine));
                if (iLine == this.Start.iLine || iLine == this.End.iLine)
                {
                    r = r.GetIntersectionWith(this);
                }

                foreach (var foundRange in r.GetRanges(regex))
                {
                    yield return foundRange;
                }

                if (!isLineLoaded)
                {
                    fts.UnloadLine(iLine);
                }
            }
        }

        /// <summary>
        /// 	Finds ranges for given regex pattern.
        /// 	Search is separately in each line (order of lines is reversed).
        /// 	This method requires less memory than GetRanges().
        /// </summary>
        /// <param name="regexPattern"> Regex pattern </param>
        /// <returns> Enumeration of ranges </returns>
        public IEnumerable<Range> GetRangesByLinesReversed(string regexPattern, RegexOptions options)
        {
            this.Normalize();
            //create regex
            var regex = new Regex(regexPattern, options);
            //
            var fts = this.tb.TextSource as FileTextSource; //<----!!!! ugly

            //enumaerate lines
            for (int iLine = this.End.iLine; iLine >= this.Start.iLine; iLine--)
            {
                //
                bool isLineLoaded = fts != null ? fts.IsLineLoaded(iLine) : true;
                //
                var r = new Range(this.tb, new Place(0, iLine), new Place(this.tb[iLine].Count, iLine));
                if (iLine == this.Start.iLine || iLine == this.End.iLine)
                {
                    r = r.GetIntersectionWith(this);
                }

                var list = new List<Range>();

                foreach (var foundRange in r.GetRanges(regex))
                {
                    list.Add(foundRange);
                }

                for (int i = list.Count - 1; i >= 0; i--)
                {
                    yield return list[i];
                }

                if (!isLineLoaded)
                {
                    fts.UnloadLine(iLine);
                }
            }
        }

        /// <summary>
        /// 	Finds ranges for given regex
        /// </summary>
        /// <returns> Enumeration of ranges </returns>
        public IEnumerable<Range> GetRanges(Regex regex)
        {
            //get text
            string text;
            List<Place> charIndexToPlace;
            this.GetText(out text, out charIndexToPlace);
            //
            foreach (Match m in regex.Matches(text))
            {
                var r = new Range(this.tb);
                //try get 'range' group, otherwise use group 0
                Group group = m.Groups["range"];
                if (!group.Success)
                {
                    group = m.Groups[0];
                }
                //
                r.Start = charIndexToPlace[group.Index];
                r.End = charIndexToPlace[group.Index + group.Length];
                yield return r;
            }
        }

        /// <summary>
        /// 	Clear styles of range
        /// </summary>
        public void ClearStyle(params Style[] styles)
        {
            try
            {
                this.ClearStyle(this.tb.GetStyleIndexMask(styles));
            }
            catch
            {
                ;
            }
        }

        /// <summary>
        /// 	Clear styles of range
        /// </summary>
        public void ClearStyle(StyleIndex styleIndex)
        {
            //set code to chars
            int fromLine = Math.Min(this.End.iLine, this.Start.iLine);
            int toLine = Math.Max(this.End.iLine, this.Start.iLine);
            int fromChar = this.FromX;
            int toChar = this.ToX;
            if (fromLine < 0)
            {
                return;
            }
            //
            for (int y = fromLine; y <= toLine; y++)
            {
                int fromX = y == fromLine ? fromChar : 0;
                int toX = y == toLine ? Math.Min(toChar - 1, this.tb[y].Count - 1) : this.tb[y].Count - 1;
                for (int x = fromX; x <= toX; x++)
                {
                    Char c = this.tb[y][x];
                    c.style &= ~styleIndex;
                    this.tb[y][x] = c;
                }
            }
            //
            this.tb.Invalidate();
        }

        /// <summary>
        /// 	Clear folding markers of all lines of range
        /// </summary>
        public void ClearFoldingMarkers()
        {
            //set code to chars
            int fromLine = Math.Min(this.End.iLine, this.Start.iLine);
            int toLine = Math.Max(this.End.iLine, this.Start.iLine);
            if (fromLine < 0)
            {
                return;
            }
            //
            for (int y = fromLine; y <= toLine; y++)
            {
                this.tb[y].ClearFoldingMarkers();
            }
            //
            this.tb.Invalidate();
        }

        private void OnSelectionChanged()
        {
            //clear cache
            this.cachedTextVersion = -1;
            this.cachedText = null;
            this.cachedCharIndexToPlace = null;
            //
            if (this.tb.Selection == this)
            {
                if (this.updating == 0)
                {
                    this.tb.OnSelectionChanged();
                }
            }
        }

        /// <summary>
        /// 	Starts selection position updating
        /// </summary>
        public void BeginUpdate()
        {
            this.updating++;
        }

        /// <summary>
        /// 	Ends selection position updating
        /// </summary>
        public void EndUpdate()
        {
            this.updating--;
            if (this.updating == 0)
            {
                this.OnSelectionChanged();
            }
        }

        public override string ToString()
        {
            return "Start: " + this.Start + " End: " + this.End;
        }

        /// <summary>
        /// 	Exchanges Start and End if End appears before Start
        /// </summary>
        public void Normalize()
        {
            if (this.Start > this.End)
            {
                this.Inverse();
            }
        }

        /// <summary>
        /// 	Exchanges Start and End
        /// </summary>
        public void Inverse()
        {
            Place temp = this.start;
            this.start = this.end;
            this.end = temp;
        }

        /// <summary>
        /// 	Expands range from first char of Start line to last char of End line
        /// </summary>
        public void Expand()
        {
            this.Normalize();
            this.start = new Place(0, this.start.iLine);
            this.end = new Place(this.tb.GetLineLength(this.end.iLine), this.end.iLine);
        }

        IEnumerator<Place> IEnumerable<Place>.GetEnumerator()
        {
            if (this.ColumnSelectionMode)
            {
                foreach (var p in this.GetEnumerator_ColumnSelectionMode())
                {
                    yield return p;
                }
                yield break;
            }

            int fromLine = Math.Min(this.end.iLine, this.start.iLine);
            int toLine = Math.Max(this.end.iLine, this.start.iLine);
            int fromChar = this.FromX;
            int toChar = this.ToX;
            if (fromLine < 0)
            {
                yield break;
            }
            //
            for (int y = fromLine; y <= toLine; y++)
            {
                int fromX = y == fromLine ? fromChar : 0;
                int toX = y == toLine ? Math.Min(toChar - 1, this.tb[y].Count - 1) : this.tb[y].Count - 1;
                for (int x = fromX; x <= toX; x++)
                {
                    yield return new Place(x, y);
                }
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return (this as IEnumerable<Place>).GetEnumerator();
        }

        /// <summary>
        /// 	Chars of range (exclude \n)
        /// </summary>
        public IEnumerable<Char> Chars
        {
            get
            {
                if (this.ColumnSelectionMode)
                {
                    foreach (var p in this.GetEnumerator_ColumnSelectionMode())
                    {
                        yield return this.tb[p];
                    }
                    yield break;
                }

                int fromLine = Math.Min(this.end.iLine, this.start.iLine);
                int toLine = Math.Max(this.end.iLine, this.start.iLine);
                int fromChar = this.FromX;
                int toChar = this.ToX;
                if (fromLine < 0)
                {
                    yield break;
                }
                //
                for (int y = fromLine; y <= toLine; y++)
                {
                    int fromX = y == fromLine ? fromChar : 0;
                    int toX = y == toLine ? Math.Min(toChar - 1, this.tb[y].Count - 1) : this.tb[y].Count - 1;
                    Line line = this.tb[y];
                    for (int x = fromX; x <= toX; x++)
                    {
                        yield return line[x];
                    }
                }
            }
        }

        /// <summary>
        /// 	Get fragment of text around Start place. Returns maximal matched to pattern fragment.
        /// </summary>
        /// <param name="allowedSymbolsPattern"> Allowed chars pattern for fragment </param>
        /// <returns> Range of found fragment </returns>
        public Range GetFragment(string allowedSymbolsPattern)
        {
            return this.GetFragment(allowedSymbolsPattern, RegexOptions.None);
        }

        /// <summary>
        /// 	Get fragment of text around Start place. Returns maximal matched to given Style.
        /// </summary>
        /// <param name="style"> Allowed style for fragment </param>
        /// <returns> Range of found fragment </returns>
        public Range GetFragment(Style style, bool allowLineBreaks)
        {
            StyleIndex mask = this.tb.GetStyleIndexMask(new[] { style });
            //
            var r = new Range(this.tb);
            r.Start = this.Start;
            //go left, check style
            while (r.GoLeftThroughFolded())
            {
                if (!allowLineBreaks && r.CharAfterStart == '\n')
                {
                    break;
                }
                if (r.Start.iChar < this.tb.GetLineLength(r.Start.iLine))
                {
                    if ((this.tb[r.Start].style & mask) == 0)
                    {
                        r.GoRightThroughFolded();
                        break;
                    }
                }
            }
            Place startFragment = r.Start;

            r.Start = this.Start;
            //go right, check style
            do
            {
                if (!allowLineBreaks && r.CharAfterStart == '\n')
                {
                    break;
                }
                if (r.Start.iChar < this.tb.GetLineLength(r.Start.iLine))
                {
                    if ((this.tb[r.Start].style & mask) == 0)
                    {
                        break;
                    }
                }
            }
            while (r.GoRightThroughFolded());
            Place endFragment = r.Start;

            return new Range(this.tb, startFragment, endFragment);
        }

        /// <summary>
        /// 	Get fragment of text around Start place. Returns maximal mathed to pattern fragment.
        /// </summary>
        /// <param name="allowedSymbolsPattern"> Allowed chars pattern for fragment </param>
        /// <returns> Range of found fragment </returns>
        public Range GetFragment(string allowedSymbolsPattern, RegexOptions options)
        {
            var r = new Range(this.tb);
            r.Start = this.Start;
            var regex = new Regex(allowedSymbolsPattern, options);
            //go left, check symbols
            while (r.GoLeftThroughFolded())
            {
                if (!regex.IsMatch(r.CharAfterStart.ToString()))
                {
                    r.GoRightThroughFolded();
                    break;
                }
            }
            Place startFragment = r.Start;

            r.Start = this.Start;
            //go right, check symbols
            do
            {
                if (!regex.IsMatch(r.CharAfterStart.ToString()))
                {
                    break;
                }
            }
            while (r.GoRightThroughFolded());
            Place endFragment = r.Start;

            return new Range(this.tb, startFragment, endFragment);
        }

        private bool IsIdentifierChar(char c)
        {
            return char.IsLetterOrDigit(c) || c == '_';
        }

        private bool IsSpaceChar(char c)
        {
            return c == ' ' || c == '\t';
        }

        public void GoWordLeft(bool shift)
        {
            this.ColumnSelectionMode = false;

            if (!shift && this.start > this.end)
            {
                this.Start = this.End;
                return;
            }

            Range range = this.Clone(); //for OnSelectionChanged disable
            bool wasSpace = false;
            while (this.IsSpaceChar(range.CharBeforeStart))
            {
                wasSpace = true;
                range.GoLeft(shift);
            }
            bool wasIdentifier = false;
            while (this.IsIdentifierChar(range.CharBeforeStart))
            {
                wasIdentifier = true;
                range.GoLeft(shift);
            }
            if (!wasIdentifier && (!wasSpace || range.CharBeforeStart != '\n'))
            {
                range.GoLeft(shift);
            }
            this.Start = range.Start;
            this.End = range.End;

            if (this.tb.LineInfos[this.Start.iLine].VisibleState != VisibleState.Visible)
            {
                this.GoRight(shift);
            }
        }

        public void GoWordRight(bool shift)
        {
            this.ColumnSelectionMode = false;

            if (!shift && this.start < this.end)
            {
                this.Start = this.End;
                return;
            }

            Range range = this.Clone(); //for OnSelectionChanged disable
            bool wasSpace = false;
            while (this.IsSpaceChar(range.CharAfterStart))
            {
                wasSpace = true;
                range.GoRight(shift);
            }
            bool wasIdentifier = false;
            while (this.IsIdentifierChar(range.CharAfterStart))
            {
                wasIdentifier = true;
                range.GoRight(shift);
            }
            if (!wasIdentifier && (!wasSpace || range.CharAfterStart != '\n'))
            {
                range.GoRight(shift);
            }
            this.Start = range.Start;
            this.End = range.End;

            if (this.tb.LineInfos[this.Start.iLine].VisibleState != VisibleState.Visible)
            {
                this.GoLeft(shift);
            }
        }

        internal void GoFirst(bool shift)
        {
            this.ColumnSelectionMode = false;

            this.start = new Place(0, 0);
            if (this.tb.LineInfos[this.Start.iLine].VisibleState != VisibleState.Visible)
            {
                this.GoRight(shift);
            }
            if (!shift)
            {
                this.end = this.start;
            }

            this.OnSelectionChanged();
        }

        internal void GoLast(bool shift)
        {
            this.ColumnSelectionMode = false;

            this.start = new Place(this.tb[this.tb.LinesCount - 1].Count, this.tb.LinesCount - 1);
            if (this.tb.LineInfos[this.Start.iLine].VisibleState != VisibleState.Visible)
            {
                this.GoLeft(shift);
            }
            if (!shift)
            {
                this.end = this.start;
            }

            this.OnSelectionChanged();
        }

        public static StyleIndex ToStyleIndex(int i)
        {
            return (StyleIndex)(1 << i);
        }

        public RangeRect Bounds
        {
            get
            {
                int minX = Math.Min(this.Start.iChar, this.End.iChar);
                int minY = Math.Min(this.Start.iLine, this.End.iLine);
                int maxX = Math.Max(this.Start.iChar, this.End.iChar);
                int maxY = Math.Max(this.Start.iLine, this.End.iLine);
                return new RangeRect(minY, minX, maxY, maxX);
            }
        }

        public IEnumerable<Range> GetSubRanges(bool includeEmpty)
        {
            if (!this.ColumnSelectionMode)
            {
                yield return this;
                yield break;
            }

            RangeRect rect = this.Bounds;
            for (int y = rect.iStartLine; y <= rect.iEndLine; y++)
            {
                if (rect.iStartChar > this.tb[y].Count && !includeEmpty)
                {
                    continue;
                }

                var r = new Range(this.tb, rect.iStartChar, y, Math.Min(rect.iEndChar, this.tb[y].Count), y);
                yield return r;
            }
        }

        /// <summary>
        /// 	Range is readonly?
        /// 	This property return True if any char of the range contains ReadOnlyStyle.
        /// 	Set this property to True/False to mark chars of the range as Readonly/Writable.
        /// </summary>
        public bool ReadOnly
        {
            get
            {
                if (this.tb.ReadOnly)
                {
                    return true;
                }

                ReadOnlyStyle readonlyStyle = null;
                foreach (var style in this.tb.Styles)
                {
                    if (style is ReadOnlyStyle)
                    {
                        readonlyStyle = (ReadOnlyStyle)style;
                        break;
                    }
                }

                if (readonlyStyle != null)
                {
                    StyleIndex si = ToStyleIndex(this.tb.GetStyleIndex(readonlyStyle));

                    if (this.IsEmpty)
                    {
                        //check previous and next chars
                        Line line = this.tb[this.start.iLine];
                        if (this.columnSelectionMode)
                        {
                            foreach (var sr in this.GetSubRanges(false))
                            {
                                line = this.tb[sr.start.iLine];
                                if (sr.start.iChar < line.Count && sr.start.iChar > 0)
                                {
                                    Char left = line[sr.start.iChar - 1];
                                    Char right = line[sr.start.iChar];
                                    if ((left.style & si) != 0 && (right.style & si) != 0)
                                    {
                                        return true; //we are between readonly chars
                                    }
                                }
                            }
                        }
                        else if (this.start.iChar < line.Count && this.start.iChar > 0)
                        {
                            Char left = line[this.start.iChar - 1];
                            Char right = line[this.start.iChar];
                            if ((left.style & si) != 0 && (right.style & si) != 0)
                            {
                                return true; //we are between readonly chars
                            }
                        }
                    }
                    else
                    {
                        foreach (var c in this.Chars)
                        {
                            if ((c.style & si) != 0) //found char with ReadonlyStyle
                            {
                                return true;
                            }
                        }
                    }
                }

                return false;
            }

            set
            {
                //find exists ReadOnlyStyle of style buffer
                ReadOnlyStyle readonlyStyle = null;
                foreach (var style in this.tb.Styles)
                {
                    if (style is ReadOnlyStyle)
                    {
                        readonlyStyle = (ReadOnlyStyle)style;
                        break;
                    }
                }

                //create ReadOnlyStyle
                if (readonlyStyle == null)
                {
                    readonlyStyle = new ReadOnlyStyle();
                }

                //set/clear style
                if (value)
                {
                    SetStyle(readonlyStyle);
                }
                else
                {
                    ClearStyle(readonlyStyle);
                }
            }
        }

        /// <summary>
        /// 	Is char before range readonly
        /// </summary>
        /// <returns> </returns>
        public bool IsReadOnlyLeftChar()
        {
            if (this.tb.ReadOnly)
            {
                return true;
            }

            Range r = this.Clone();

            r.Normalize();
            if (r.start.iChar == 0)
            {
                return false;
            }
            if (this.ColumnSelectionMode)
            {
                r.GoLeft_ColumnSelectionMode();
            }
            else
            {
                r.GoLeft(true);
            }

            return r.ReadOnly;
        }

        /// <summary>
        /// 	Is char after range readonly
        /// </summary>
        /// <returns> </returns>
        public bool IsReadOnlyRightChar()
        {
            if (this.tb.ReadOnly)
            {
                return true;
            }

            Range r = this.Clone();

            r.Normalize();
            if (r.end.iChar >= this.tb[this.end.iLine].Count)
            {
                return false;
            }
            if (this.ColumnSelectionMode)
            {
                r.GoRight_ColumnSelectionMode();
            }
            else
            {
                r.GoRight(true);
            }

            return r.ReadOnly;
        }

        public IEnumerable<Place> GetPlacesCyclic(Place startPlace, bool backward = false)
        {
            if (backward)
            {
                var r = new Range(this.tb, startPlace, startPlace);
                while (r.GoLeft() && r.start >= this.Start)
                {
                    if (r.Start.iChar < this.tb[r.Start.iLine].Count)
                    {
                        yield return r.Start;
                    }
                }

                r = new Range(this.tb, this.End, this.End);
                while (r.GoLeft() && r.start >= startPlace)
                {
                    if (r.Start.iChar < this.tb[r.Start.iLine].Count)
                    {
                        yield return r.Start;
                    }
                }
            }
            else
            {
                var r = new Range(this.tb, startPlace, startPlace);
                if (startPlace < this.End)
                {
                    do
                    {
                        if (r.Start.iChar < this.tb[r.Start.iLine].Count)
                        {
                            yield return r.Start;
                        }
                    }
                    while (r.GoRight());
                }

                r = new Range(this.tb, this.Start, this.Start);
                if (r.Start < startPlace)
                {
                    do
                    {
                        if (r.Start.iChar < this.tb[r.Start.iLine].Count)
                        {
                            yield return r.Start;
                        }
                    }
                    while (r.GoRight() && r.Start < startPlace);
                }
            }
        }

        #region ColumnSelectionMode

        private Range GetIntersectionWith_ColumnSelectionMode(Range range)
        {
            if (range.Start.iLine != range.End.iLine)
            {
                return new Range(this.tb, this.Start, this.Start);
            }
            RangeRect rect = this.Bounds;
            if (range.Start.iLine < rect.iStartLine || range.Start.iLine > rect.iEndLine)
            {
                return new Range(this.tb, this.Start, this.Start);
            }

            return
                new Range(this.tb, rect.iStartChar, range.Start.iLine, rect.iEndChar, range.Start.iLine).
                    GetIntersectionWith(range);
        }

        private bool GoRightThroughFolded_ColumnSelectionMode()
        {
            RangeRect boundes = this.Bounds;
            bool endOfLines = true;
            for (int iLine = boundes.iStartLine; iLine <= boundes.iEndLine; iLine++)
            {
                if (boundes.iEndChar < this.tb[iLine].Count)
                {
                    endOfLines = false;
                    break;
                }
            }

            if (endOfLines)
            {
                return false;
            }

            Place start = this.Start;
            Place end = this.End;
            start.Offset(1, 0);
            end.Offset(1, 0);
            this.BeginUpdate();
            this.Start = start;
            this.End = end;
            this.EndUpdate();

            return true;
        }

        private IEnumerable<Place> GetEnumerator_ColumnSelectionMode()
        {
            RangeRect bounds = this.Bounds;
            if (bounds.iStartLine < 0)
            {
                yield break;
            }
            //
            for (int y = bounds.iStartLine; y <= bounds.iEndLine; y++)
            {
                for (int x = bounds.iStartChar; x < bounds.iEndChar; x++)
                {
                    if (x < this.tb[y].Count)
                    {
                        yield return new Place(x, y);
                    }
                }
            }
        }

        private string Text_ColumnSelectionMode
        {
            get
            {
                var sb = new StringBuilder();
                RangeRect bounds = this.Bounds;
                if (bounds.iStartLine < 0)
                {
                    return "";
                }
                //
                for (int y = bounds.iStartLine; y <= bounds.iEndLine; y++)
                {
                    for (int x = bounds.iStartChar; x < bounds.iEndChar; x++)
                    {
                        if (x < this.tb[y].Count)
                        {
                            sb.Append(this.tb[y][x].c);
                        }
                    }
                    if (bounds.iEndLine != bounds.iStartLine && y != bounds.iEndLine)
                    {
                        sb.AppendLine();
                    }
                }

                return sb.ToString();
            }
        }

        internal void GoDown_ColumnSelectionMode()
        {
            int iLine = this.tb.FindNextVisibleLine(this.End.iLine);
            this.End = new Place(this.End.iChar, iLine);
        }

        internal void GoUp_ColumnSelectionMode()
        {
            int iLine = this.tb.FindPrevVisibleLine(this.End.iLine);
            this.End = new Place(this.End.iChar, iLine);
        }

        internal void GoRight_ColumnSelectionMode()
        {
            this.End = new Place(this.End.iChar + 1, this.End.iLine);
        }

        internal void GoLeft_ColumnSelectionMode()
        {
            if (this.End.iChar > 0)
            {
                this.End = new Place(this.End.iChar - 1, this.End.iLine);
            }
        }

        #endregion
    }

    public struct RangeRect
    {
        public RangeRect(int iStartLine, int iStartChar, int iEndLine, int iEndChar)
        {
            this.iStartLine = iStartLine;
            this.iStartChar = iStartChar;
            this.iEndLine = iEndLine;
            this.iEndChar = iEndChar;
        }

        public int iStartLine;

        public int iStartChar;

        public int iEndLine;

        public int iEndChar;
    }
}